﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using ThreeByte.ActiveDeck.Service;
using ThreeByte.ActiveDeck.Data.UserResponse;
using System.IO;
using ThreeByte.Media;
using System.Windows.Threading;
using System.Windows;
using ThreeByte.ActiveDeck.Config;
using log4net;
using System.Threading;

namespace ThreeByte.ActiveDeck.AddIn.Content.UserResponse
{
    public class AnimatedTextUpdater : UserResponseFeedbackUpdater
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(AnimatedTextUpdater));

        private PowerPoint.TextFrame MyTextFrame;

        private QuestionConfig QuestionConfig;
        private ShortAnswerUserData UserData;
        private string TextDisplayMode;
        private bool ClickToAdvance = false;

        public AnimatedTextUpdater(string presentation, PowerPoint.Shape shape, WebServiceClient client, QuestionConfig questionConfig)
            : base(presentation, shape, client) {

            MyTextFrame = shape.TextFrame;
            TextDisplayMode = shape.Tags["Mode"];
            bool.TryParse(shape.Tags["ClickToAdvance"], out ClickToAdvance);

            MyTextFrame.TextRange.Text = string.Empty;  //Clear the Placeholder text

            QuestionConfig = questionConfig;
            UserData = questionConfig.InitUserData();
            if(TextDisplayMode == "Cycle") {
                UserData.Repeat = true;
            }

            //Find any other animation dummies on this slide and remove them.
            PowerPoint.Slide thisSlide = (PowerPoint.Slide)(MyShape.Parent);
            List<PowerPoint.Shape> shapesToDelete = (from PowerPoint.Shape s in thisSlide.Shapes
                                                    where s.Name == "AnimationDummy" || s.Name == "ADVANCE_ARROW"
                                                    select s).ToList();
            foreach(PowerPoint.Shape s in shapesToDelete){
                s.Delete();
            }

            Globals.ThisAddIn.Application.SlideShowNextBuild += new PowerPoint.EApplication_SlideShowNextBuildEventHandler(Application_SlideShowNextBuild);
        }

        private int currentBuildCount = 0;
        void Application_SlideShowNextBuild(PowerPoint.SlideShowWindow Wn) {
            if(!Active) {
                //We are not on this slide, don't do anything
                //(Like delete all of the pretty animations, that someone worked really hard to construct)
                return;
            }
            try {
                //log.InfoFormat("Build Current Click index: {0}", Wn.View.GetClickIndex());

                //Delete advance shapes if they exist
                PowerPoint.Slide thisSlide = (PowerPoint.Slide)(MyShape.Parent);
                List<PowerPoint.Shape> shapesToDelete = (from PowerPoint.Shape s in thisSlide.Shapes
                                                         where s.Name == "ADVANCE_ARROW"
                                                         select s).ToList();
                foreach(PowerPoint.Shape s in shapesToDelete) {
                    log.Info("Deleting Advance Arrow");
                    s.Delete();
                }

                

                if(++currentBuildCount >= configuredEffectCount) {
                    log.Debug("Build Count Hit");

                    PowerPoint.Sequence mSequence = Wn.View.Slide.TimeLine.MainSequence;
                    DeleteAllAnimations(mSequence);

                    foreach(PowerPoint.Shape s in _shapesToUpdate) {
                        if(TextDisplayMode == "Last" || TextDisplayMode == "Cycle") {
                            string newText = s.Tags["NextText"];
                            log.DebugFormat("Updating Text {0}", newText);
                            s.TextFrame.TextRange.Text = newText;
                        } else if(TextDisplayMode == "Accumulate") {
                            int newTop = int.Parse(s.Tags["Top"]);
                            log.DebugFormat("Updating New Top {0}", newTop);
                            s.Top = newTop;
                        }
                    }
                    
                    _shapesToUpdate.Clear();

                    //if (isFadeOut) {
                    //    //Update the object
                    //    ConstructEntryAnimation(mSequence);
                    //    Wn.View.GotoClick(0);
                    //    Wn.View.Next();//Kick it off

                    //} else {


                        log.Debug("Clearing the animation builder gate");
                        _animationPendingEvent.Set();

                        if (ClickToAdvance && UserData.HasNext()) {
                            //Make sure to invoke the update again right away
                            log.Debug("Try to Update graphic again immediately");
                            ThreadPool.QueueUserWorkItem((WaitCallback)delegate(object o) { UpdateGraphic(); });
                        }
                    //}
                }
            } catch(Exception ex) {
                log.Warn("Problem on next build", ex);
            }
        }

        //private void ConstructEntryAnimation(PowerPoint.Sequence sequence) {
        //    log.Debug("Construct Entry Animation");
        //    PowerPoint.Shape currentShape = FindNamedShape((PowerPoint.Slide)(MyShape.Parent), @"CURRENT_SHAPE");
        //    log.Debug("Update the text");
        //    currentShape.TextFrame.TextRange.Text = currentShape.Tags["NextText"];
        //    configuredEffectCount = AddFadeInEffect(sequence, currentShape);
        //    isFadeOut = false;
        //}

        private void DeleteAllAnimations(PowerPoint.Sequence sequence) {
            log.DebugFormat("Delete all existing animations: {0}", sequence.Count);
            for (int i = sequence.Count; i > 0; i--) {
                sequence[i].Delete();
            }
        }

        private List<PowerPoint.Shape> _shapesToUpdate = new List<PowerPoint.Shape>();

        protected override bool UpdateUserData(System.Xml.Linq.XElement userData) {
            AggregateUserData newUserData = AggregateUserData.ParseXml(userData);
            bool update = UserData.UpdateUserData(newUserData);
            return (update || UserData.HasNext()); //Trigger redraw if the info was updated or if there are additional responses available
        }

        private int instance = 0;
        private int slideHeight;
        private int slideWidth;
        private AutoResetEvent _animationPendingEvent = new AutoResetEvent(true);
        private bool firstAnimation = true;
        //private bool isFadeOut = false;
        private int configuredEffectCount = 0;

        protected override void UpdateGraphic() {
            
            if(!_animationPendingEvent.WaitOne(0)) {
                log.Debug("An Animation is still pending - don't update anything yet");
                return;
            }

            

            string newText = UserData.GetNext();
            log.DebugFormat("Updating user text: {0}", newText);
            if(string.IsNullOrWhiteSpace(newText)) {
                //No new text to show
                log.Debug("No new user response to display");
                _animationPendingEvent.Set();//Clear the gate so we could add another animation later
                return;
            }

            

            PowerPoint.SlideShowView view = ((PowerPoint.Application)(MyShape.Application)).SlideShowWindows[1].View;
            //log.InfoFormat("Before Count: {0} Index: {1}", view.GetClickCount(), view.GetClickIndex());
            
            PowerPoint.Slide thisSlide = (PowerPoint.Slide)(MyShape.Parent);
            slideHeight = (int)(thisSlide.Master.Height);
            slideWidth = (int)(thisSlide.Master.Width);
            log.InfoFormat("Slide height: {0}", slideHeight);

            //Delete all existing animations
            PowerPoint.Sequence mSequence = thisSlide.TimeLine.MainSequence;
            DeleteAllAnimations(mSequence);
            log.Debug("Reset current build count");
            currentBuildCount = 0;

            //Find or create the signal object
            PowerPoint.Shape signalShape = FindNamedShape((PowerPoint.Slide)(MyShape.Parent), @"SIGNAL_SHAPE");
            if(signalShape == null) {
                signalShape = ((PowerPoint.Slide)(MyShape.Parent)).Shapes.AddShape(Microsoft.Office.Core.MsoAutoShapeType.msoShapeCross, -20, -20, 10, 10);
                signalShape.Name = @"SIGNAL_SHAPE";
            }

            if(TextDisplayMode == "Last" || TextDisplayMode == "Cycle") {
                
                int top = (int)(MyShape.Top) + slideHeight;
                int left = (int)(MyShape.Left);

                MyShape.Tags.Add("NextText", newText);
                _shapesToUpdate.Add(MyShape);

                PowerPoint.Effect finalEffect = mSequence.AddEffect(signalShape, PowerPoint.MsoAnimEffect.msoAnimEffectAppear);
                finalEffect.Exit = Microsoft.Office.Core.MsoTriState.msoTrue;
                configuredEffectCount = 1;

                //COMPLICATED ANIMATION THING HERE *******************
                ////Find previous animation box
                //PowerPoint.Shape currentShape = FindNamedShape((PowerPoint.Slide)(MyShape.Parent), @"CURRENT_SHAPE");
                //if (currentShape != null) {
                //    //Add a fade out effect and set the process in motion
                //    currentShape.Tags.Add("NextText", newText);
                //    log.Debug("Construct animation to Fade out");
                //    configuredEffectCount = AddFadeOutEffect(mSequence, currentShape);
                //    //Set a flag so we know how to handle this when we get the sign that it happened
                //    _shapesToUpdate.Add(currentShape);
                //    currentShape.Tags.Add("Top", string.Format("{0}", top));

                //    isFadeOut = true;
                //    //view.Next();
                //} else {
                //    //This generally only happens the first time
                //    log.Debug("Construct shape offscreen");

                //    //Otherwise, create a new shape
                //    //Fix the height of this text box
                //    PowerPoint.Shape newShape = ((PowerPoint.Slide)(MyShape.Parent)).Shapes.AddTextbox(Microsoft.Office.Core.MsoTextOrientation.msoTextOrientationHorizontal, left, top, MyShape.Width, MyShape.Height);
                //    newShape.TextFrame.TextRange.Text = newText;
                //    _shapesToUpdate.Add(newShape);
                //    newShape.Tags.Add("Top", string.Format("{0}", top - slideHeight));
                //    newShape.Name = "CURRENT_SHAPE";

                //    //And bring it on screen
                //    int effectCount = AddFadeInEffect(mSequence, newShape);
                //    isFadeOut = false;
                //    configuredEffectCount = effectCount;
                //}


            //} else if(TextDisplayMode == "Cycle") {


            } else if(TextDisplayMode == "Accumulate") {
                log.Debug("Construct shape offscreen");

                int top = (int)(MyShape.Top) + (30 * (instance++)) + slideHeight;
                int left = (int)(MyShape.Left);

                PowerPoint.Shape newShape = ((PowerPoint.Slide)(MyShape.Parent)).Shapes.AddTextbox(Microsoft.Office.Core.MsoTextOrientation.msoTextOrientationHorizontal, left, top, 240, 60);
                newShape.TextFrame.TextRange.Text = newText;
                _shapesToUpdate.Add(newShape);
                newShape.Tags.Add("Top", string.Format("{0}", top - slideHeight));
                newShape.Name = "AnimationDummy";

                log.DebugFormat("The Shape Height is: {0}", newShape.Height);
                log.Debug("Construct animation to bring onscreen");

                PowerPoint.Effect firstEffect = mSequence.AddEffect(newShape, PowerPoint.MsoAnimEffect.msoAnimEffectPathUp);  //Defaults are OK for the first effect
                firstEffect.Behaviors[1].MotionEffect.Path = "M 0 0 L 0 -1 E";

                log.Debug("Construct a follow on animation");
                PowerPoint.Effect secondEffect = mSequence.AddEffect(newShape, PowerPoint.MsoAnimEffect.msoAnimEffectBoldFlash,
                                                                                PowerPoint.MsoAnimateByLevel.msoAnimateLevelNone,
                                                                                PowerPoint.MsoAnimTriggerType.msoAnimTriggerAfterPrevious);
                //secondEffect.Timing.Duration = 0.5f;
                configuredEffectCount = 2;

            }


            log.Debug("Go to the top of the slide");
            //view.GotoClick((int)PowerPoint.MsoClickState.msoClickStateBeforeAutomaticAnimations);
            view.GotoClick(0);
            

            if(ClickToAdvance && !firstAnimation) {
                //Create and display a new shape
                PowerPoint.Shape advanceShape = ((PowerPoint.Slide)(MyShape.Parent)).Shapes.AddShape(Microsoft.Office.Core.MsoAutoShapeType.msoShapeCross, slideWidth - 20, slideHeight - 20, 10, 10);
                advanceShape.Name = "ADVANCE_ARROW";

            } else {
                //Go there automatically
                log.Debug("Go next");
                view.Next();
            }

            firstAnimation = false; //We just did it.

        }

        //private static int AddFadeOutEffect(PowerPoint.Sequence sequence, PowerPoint.Shape shape) {
        //    int effectsAdded = 0;
        //    PowerPoint.Effect fadeOutEffect = sequence.AddEffect(shape, PowerPoint.MsoAnimEffect.msoAnimEffectFade);
        //    fadeOutEffect.Exit = Microsoft.Office.Core.MsoTriState.msoTrue;
        //    effectsAdded++;

        //    PowerPoint.Effect travelEffect = sequence.AddEffect(shape, PowerPoint.MsoAnimEffect.msoAnimEffectPathUp,
        //                                                                    PowerPoint.MsoAnimateByLevel.msoAnimateLevelNone,
        //                                                                    PowerPoint.MsoAnimTriggerType.msoAnimTriggerAfterPrevious);
        //    travelEffect.Behaviors[1].MotionEffect.Path = "M 0 0 L 0 1 E";
        //    effectsAdded++;

        //    PowerPoint.Effect reappearEffect = sequence.AddEffect(shape, PowerPoint.MsoAnimEffect.msoAnimEffectAppear,
        //                                                                    PowerPoint.MsoAnimateByLevel.msoAnimateLevelNone,
        //                                                                    PowerPoint.MsoAnimTriggerType.msoAnimTriggerAfterPrevious);
        //    effectsAdded++;

        //    return effectsAdded;
        //}

        //private static int AddFadeInEffect(PowerPoint.Sequence sequence, PowerPoint.Shape shape) {
        //    int effectsAdded = 0;
        //    PowerPoint.Effect firstEffect = sequence.AddEffect(shape, PowerPoint.MsoAnimEffect.msoAnimEffectPathUp); //Defaults are OK for the first effect
        //    firstEffect.Behaviors[1].MotionEffect.Path = "M 0 0 L 0 -1 E";

        //    PowerPoint.AnimationBehavior disappear = firstEffect.Behaviors.Add(PowerPoint.MsoAnimType.msoAnimTypeSet, 1);
        //    disappear.SetEffect.Property = PowerPoint.MsoAnimProperty.msoAnimVisibility;
        //    disappear.SetEffect.To = Microsoft.Office.Core.MsoTriState.msoFalse;

        //    firstEffect.Timing.Duration = 0.1f;
        //    effectsAdded++;

        //    log.Debug("Construct a follow on animation");
        //    PowerPoint.Effect fadeInEffect = sequence.AddEffect(shape, PowerPoint.MsoAnimEffect.msoAnimEffectFade,
        //                                                                    PowerPoint.MsoAnimateByLevel.msoAnimateLevelNone,
        //                                                                    PowerPoint.MsoAnimTriggerType.msoAnimTriggerAfterPrevious);
        //    fadeInEffect.Timing.Duration = 1.0f;
        //    effectsAdded++;

        //    PowerPoint.Effect secondEffect = sequence.AddEffect(shape, PowerPoint.MsoAnimEffect.msoAnimEffectBoldFlash,
        //                                                                        PowerPoint.MsoAnimateByLevel.msoAnimateLevelNone,
        //                                                                        PowerPoint.MsoAnimTriggerType.msoAnimTriggerAfterPrevious);

        //    //PowerPoint.Shape signalShape = FindNamedShape((PowerPoint.Slide)(shape.Parent), @"SIGNAL_SHAPE");

        //    //PowerPoint.Effect finalEffect = sequence.AddEffect(signalShape, PowerPoint.MsoAnimEffect.msoAnimEffectAppear,
        //    //                                                                PowerPoint.MsoAnimateByLevel.msoAnimateLevelNone,
        //    //                                                                PowerPoint.MsoAnimTriggerType.msoAnimTriggerAfterPrevious);
        //    //finalEffect.Exit = Microsoft.Office.Core.MsoTriState.msoTrue;
        //    effectsAdded++;

        //    return effectsAdded;
        //}

        private static PowerPoint.Shape FindNamedShape(PowerPoint.Slide slide, string name) {
            foreach (PowerPoint.Shape s in slide.Shapes) {
                if (s.Name == name) {
                    return s;
                }
            }
            return null;
        }

        private void EnumerateAllEffects(PowerPoint.Slide slide) {
            log.Info("MainSequence");
            foreach(PowerPoint.Effect e in slide.TimeLine.MainSequence) {
                log.InfoFormat("Effect: {0} ({1}) --> {2}", e.DisplayName, e.EffectType, e.EffectInformation.AfterEffect);
                
                foreach(PowerPoint.AnimationBehavior b in e.Behaviors) {
                    log.InfoFormat("Behaviors: {0} - {1}", b.Type, b.Timing.Duration);
                    if(b.Type == PowerPoint.MsoAnimType.msoAnimTypeProperty) {
                        log.InfoFormat("From: {0} To: {1} Property: {2}", b.PropertyEffect.From, b.PropertyEffect.To, b.PropertyEffect.Property);
                        foreach(PowerPoint.AnimationPoint p in b.PropertyEffect.Points){
                            log.InfoFormat("{0} @ {1} ({2})", p.Value, p.Time, p.Formula);
                        }
                    }
                }
            }
            log.Info("InteractiveSequence");
            foreach(PowerPoint.Sequence s in slide.TimeLine.InteractiveSequences) {
                //s.ConvertToBuildLevel
                foreach(PowerPoint.Effect e in s) {
                    log.InfoFormat("Effect: {0} ({1}) --> {2}", e.DisplayName, e.EffectType, e.EffectInformation.AfterEffect);
                }
            }
        }


        public override void Dispose() {
            Globals.ThisAddIn.Application.SlideShowNextBuild -= Application_SlideShowNextBuild;
            
            base.Dispose();
        }
    }
}
